/**
 * Copyright © 2011 Creativor Studio.
 * About license information, please see LICENSE.txt.
 */

package org.creativor.rayson.client.shell;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.StringTokenizer;

/**
 * Rayson client shell. It is used to invoke RPC in a console shell.
 * 
 * @author Nick Zhang
 */
public final class RaysonShell
{
	private interface Command
	{
		public abstract void execute(String[] args) throws CommandExecutionException;
		
		public abstract String getDescription();
		
		public abstract String getName();
	}
	
	/**
	 * 
	 * @author Nick Zhang
	 */
	private static class CommandExecutionException extends Exception
	{
		
		/**
		 * @param message
		 */
		public CommandExecutionException(String message)
		{
			super(message);
		}
		
	}
	
	/**
	 * 
	 * @author Nick Zhang
	 */
	private static class CommandInvoker
	{
		private String[] args;
		
		private Command command;
		
		/**
		 * @param commandLine
		 */
		public CommandInvoker(String commandLine)
		{
			StringTokenizer tokenizer = new StringTokenizer(commandLine, " ");
			int tokenCount = tokenizer.countTokens();
			args = new String[tokenCount - 1];
			String commandName = null;
			String token;
			for (int i = 0; i < tokenCount; i++)
			{
				token = tokenizer.nextToken();
				if (i == 0)
					commandName = token;
				else
					args[i - 1] = token;
			}
			Command foundCmd = KNOWN_COMMANDS.get(commandName.trim().toLowerCase());
			if (foundCmd == null) foundCmd = Unknown_command;
			this.command = foundCmd;
		}
		
		/**
		 * Do invoke a command.
		 */
		public void invoke()
		{
			try
			{
				command.execute(args);
			} catch (Throwable e)
			{
				System.err.println("Invoke command " + this.toString() + " error: \n"
						+ e.getMessage());
			}
		}
		
		@Override
		public String toString()
		{
			StringBuffer sb = new StringBuffer();
			sb.append("{");
			sb.append("command: ");
			sb.append(this.command.getName());
			sb.append(", ");
			sb.append("arguments: ");
			sb.append(Arrays.toString(this.args));
			sb.append("}");
			return sb.toString();
		}
		
	}
	
	private final static Command CALL_COMMAND = new Command()
	{
		
		@Override
		public void execute(String[] args) throws CommandExecutionException
		{
			if (args == null || args.length == 0)
				throw new CommandExecutionException("Arguments error");
			String uriString = "";
			for (int i = 0; i < args.length; i++)
			{
				uriString += args[i];
				if (i + 1 != args.length) uriString += " ";
			}
			
			int firstLeftBracket = uriString.indexOf("(");
			int lastRightBracket = uriString.lastIndexOf(")");
			
			String uriPath = uriString.substring(0, firstLeftBracket);
			String parametersString = uriString.substring(firstLeftBracket + 1, lastRightBracket);
			URI uri = null;
			try
			{
				uri = new URI(uriPath);
			} catch (URISyntaxException e)
			{
				throw new CommandExecutionException(e.getMessage());
			}
			RpcInvoker invoker = new RpcInvoker(uri, parametersString);
			try
			{
				System.out.println(invoker.invoke());
				
			} catch (RpcInvokeException e)
			{
				throw new CommandExecutionException(e.getMessage());
			}
		}
		
		@Override
		public String getDescription()
		{
			return "call rayson://servername:port/service_name/methodName(parameter1_josn,parameter2_json)";
		}
		
		@Override
		public String getName()
		{
			return "call";
		}
	};
	
	private final static String COMMAND_HINT = "Rayson>";
	
	private final static Command HELP_COMMAND = new Command()
	{
		
		@Override
		public void execute(String[] args) throws CommandExecutionException
		{
			System.out.println(HELP_INFO);
		}
		
		@Override
		public String getDescription()
		{
			return "Show help information of this shell";
		}
		
		@Override
		public String getName()
		{
			return "help";
		}
	};
	
	private final static StringBuffer HELP_INFO = new StringBuffer();
	
	private final static HashMap<String, Command> KNOWN_COMMANDS =
			new HashMap<String, RaysonShell.Command>();
	
	private static final Command QUIT_COMMAND = new Command()
	{
		
		@Override
		public void execute(String[] args) throws CommandExecutionException
		{
			quit();
		}
		
		@Override
		public String getDescription()
		{
			return "Quit this shell";
		}
		
		@Override
		public String getName()
		{
			return "quit";
		}
	};
	private final static Command Unknown_command = new Command()
	{
		
		@Override
		public void execute(String[] args) throws CommandExecutionException
		{
			System.err.println("Unknown command! Please see help below:");
			System.out.println(HELP_INFO);
		}
		
		@Override
		public String getDescription()
		{
			return null;
		}
		
		@Override
		public String getName()
		{
			return null;
		}
	};
	
	static
	{
		// Set up known commands
		KNOWN_COMMANDS.put(HELP_COMMAND.getName(), HELP_COMMAND);
		KNOWN_COMMANDS.put(CALL_COMMAND.getName(), CALL_COMMAND);
		KNOWN_COMMANDS.put(QUIT_COMMAND.getName(), QUIT_COMMAND);
		// set up help information.
		for (Command command : KNOWN_COMMANDS.values())
		{
			HELP_INFO.append(command.getName());
			HELP_INFO.append("        ");
			HELP_INFO.append(command.getDescription());
			HELP_INFO.append("\n");
		}
	}
	
	public static void main(String[] args)
	{
		// Show help information first.
		System.out.println(HELP_INFO);
		String commandLine;
		while (true)
		{
			commandLine = System.console().readLine(COMMAND_HINT);
			if (commandLine == null) break;
			CommandInvoker invoker = new CommandInvoker(commandLine);
			invoker.invoke();
		}
		quit();
	}
	
	/**
	 * 
	 */
	private static void quit()
	{
		System.out.println("Bye!");
		System.exit(0);
	}
	
	private RaysonShell()
	{
		
	}
	
}
